package de.dhbw.tinf11b.vfb;

import java.util.Date;
import java.util.LinkedList;
import java.util.Map.Entry;

import de.dhbw.tinf11b.vfb.exceptions.BachelorThesisNotRatedException;
import de.dhbw.tinf11b.vfb.exceptions.MarkNotFoundException;
import de.dhbw.tinf11b.vfb.exceptions.MarkOutOfBoundsException;
import de.dhbw.tinf11b.vfb.exceptions.ModuleNotPassedException;
import de.dhbw.tinf11b.vfb.exceptions.StudyNotCompleteException;
import de.dhbw.tinf11b.vfb.exceptions.UnitNotPassedException;

public class Student {
	protected String firstName;
	protected String lastName;
	protected String homeAddress;
	protected String studentAddress;
	protected String email;
	protected Date dateOfBirth;
	protected int yearOfAbitur;
	protected String placeOfAbitur;
	protected float abiturMark;
	protected String company;
	protected String trainingManager; // entspricht Ausbildungsleiter
	protected String matriculationNumber;
	protected Course course = null;
	protected String branchOfStudy;
	protected float bachelorThesisMark = 0;
	protected LinkedList<AbstractMark> marks = new LinkedList<AbstractMark>();

	public String getFirstName() {
		return firstName;
	}
	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}
	public String getLastName() {
		return lastName;
	}
	public void setLastName(String lastName) {
		this.lastName = lastName;
	}
	public String getHomeAddress() {
		return homeAddress;
	}
	public void setHomeAddress(String homeAddress) {
		this.homeAddress = homeAddress;
	}
	public String getStudentAddress() {
		return studentAddress;
	}
	public void setStudentAddress(String studentAddress) {
		this.studentAddress = studentAddress;
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	public Date getDateOfBirth() {
		return dateOfBirth;
	}
	public void setDateOfBirth(Date dateOfBirth) {
		this.dateOfBirth = dateOfBirth;
	}
	public int getYearOfAbitur() {
		return yearOfAbitur;
	}
	public void setYearOfAbitur(int yearOfAbitur) {
		this.yearOfAbitur = yearOfAbitur;
	}
	public String getPlaceOfAbitur() {
		return placeOfAbitur;
	}
	public void setPlaceOfAbitur(String placeOfAbitur) {
		this.placeOfAbitur = placeOfAbitur;
	}
	public float getAbiturMark() {
		return abiturMark;
	}
	public void setAbiturMark(float abiturMark) {
		this.abiturMark = abiturMark;
	}
	public String getCompany() {
		return company;
	}
	public void setCompany(String company) {
		this.company = company;
	}
	public String getTrainingManager() {
		return trainingManager;
	}
	public void setTrainingManager(String trainingManager) {
		this.trainingManager = trainingManager;
	}
	public String getMatriculationNumber() {
		return matriculationNumber;
	}
	public void setMatriculationNumber(String matriculationNumber) {
		this.matriculationNumber = matriculationNumber;
	}
	public Course getCourse() {
		return course;
	}
	
	/**
	 * Immatriculate a student. Connects a student with a course.
	 * @param student
	 * @param course
	 */
	public void immatriculate(Course course) {
		if (getCourse() != null) {
			getCourse().getStudents().remove(this);
		}
		if (course.getStudents().size() == 20) {
			Course newCourse = new Course(course.getName() + "-GETEILT", course.getYear(), course.getRoom(), course.getCurriculum(), course.getCourseDirector());
			course = newCourse;
		}
		course.getStudents().add(this);
		this.course = course;
	}
	
	/**
	 * Exmatriculate a student. Sets the course of a student to null.
	 * @param student
	 */
	public void exmatriculate() {
		if (course != null) {
			this.course.getStudents().remove(this);
			this.course = null;
		}
	}
	
	public String getBranchOfStudy() {
		return branchOfStudy;
	}
	public void setBranchOfStudy(String branchOfStudy) {
		this.branchOfStudy = branchOfStudy;
	}
	public float getBachelorThesisMark() {
		return bachelorThesisMark;
	}
	public void setBachelorThesisMark(float bachelorThesisMark) {
		this.bachelorThesisMark = bachelorThesisMark;
	}
	
	public void addResultMark(Unit unit, float result) throws MarkOutOfBoundsException {
		ResultMark mark = new ResultMark(unit, result);
		this.marks.add(mark);
	}
	
	public void addPassedMark(Unit unit, boolean passed) {
		PassedMark mark = new PassedMark(unit, passed);
		this.marks.add(mark);
	}
	
	public AbstractMark getMark(Unit unit) throws MarkNotFoundException {
		for(AbstractMark mark : this.marks) {
			if(mark.getUnit().equals(unit)) {
				return mark;
			}
		}
		
		throw new MarkNotFoundException();
	}
	
	public void editResultMark(Unit unit, float result) throws MarkOutOfBoundsException {
		try {
			AbstractMark origin = this.getMark(unit);
			this.marks.remove(origin);
			this.addResultMark(unit, result);
		} catch (MarkNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * Calculates the mark of a module. If a module contains several units, the mark is the average
	 * mark of all units. Only ResultMarks are considered for the calculation. PassedMarks are just checked
	 * whether they are passed. If there are PassedMarks that are not passed, a UnitNotPassedException is thrown.
	 * @param module
	 * @return the module mark
	 * @throws MarkNotFoundException
	 * @throws UnitNotPassedException 
	 */
	public float getModuleMark(Module module) throws MarkNotFoundException, UnitNotPassedException {
		
		float markSum = 0;
		
		// calculate the module mark out of unit marks according to their weightings
		for (Entry<Unit, Float> unitEntry : module.getUnits().entrySet()) {
			Unit unit = unitEntry.getKey();
			Float weighting = unitEntry.getValue();
			AbstractMark mark = getMark(unit);
			if (mark instanceof ResultMark) {
				ResultMark resultMark = (ResultMark) mark;
				markSum = markSum + weighting * resultMark.getResult();
			} else {
				PassedMark passedMark = (PassedMark) mark;
				if (!passedMark.isPassed()) {
					throw new UnitNotPassedException(this, unit);
				}
			}
		}
		
		return markSum;
	}
	
	/**
	 * Calculates the bachelor mark for this student out of the modules marks and the bachelor thesis mark.
	 * @return
	 * @throws StudyNotCompleteException if some modules are not rated or worse than 4.0 or the bachelor thesis is not rated
	 */
	public float calculateBachelorMark() throws StudyNotCompleteException {
		
		double modulesMarkSum = 0;
		int sumCredits = getCourse().getCurriculum().getSumCredits();
		
		// check if all modules are rated and add their mark to the sum
		for (Module module : getCourse().getCurriculum().getModules()) {
			
			float mark;
			try {
				mark = getModuleMark(module);
			} catch (Exception e) {
				throw new StudyNotCompleteException(e);
			}
			
			if (mark > 4.0) {
				throw new StudyNotCompleteException(new ModuleNotPassedException(this, module));
			}
			
			modulesMarkSum = modulesMarkSum + (mark * module.getCredits() / sumCredits);
		}
		
		// check if the bachelor thesis has been rated
		if (getBachelorThesisMark() < 1) {
			throw new StudyNotCompleteException(new BachelorThesisNotRatedException());
		}
		
		// calculate the overall mark out of module marks and bachelor thesis mark
		return (float) ((modulesMarkSum * 0.8) + (getBachelorThesisMark() * 0.2));		
	}
	
	/**
	 * Issues and returns a bachelors degree for this student. Calls the method calculateBachelorMark.
	 * @return
	 * @throws StudyNotCompleteException if calculateBachelorMark throws such an exception
	 * @throws MarkOutOfBoundsException 
	 */
	public BachelorsDegree issueBachelorsDegree() throws StudyNotCompleteException, MarkOutOfBoundsException {
		float bachelorMark = calculateBachelorMark();
		BachelorsDegree bachelorsDegree = new BachelorsDegree(this, getCourse().getCurriculum().getDegreeType(), bachelorMark);
		return bachelorsDegree;
	}
	
	/**
	 * Print all marks of a student.
	 * @return
	 */
	public String printMarkList(){
		String markList = "";
		
		for (AbstractMark mark : marks){
			if (mark instanceof ResultMark) {
				ResultMark resultMark = (ResultMark) mark;
				markList = markList + resultMark.getUnit().getName() + ":" + resultMark.getResult() + "\n";
			}
			if (mark instanceof PassedMark){
				PassedMark passedMark = (PassedMark) mark;
				markList = markList + passedMark.getUnit().getName() + ":" + passedMark.getResult()+ "\n";
			}
		}
		
		return markList;
	}

	
}
